/***********************************************************************************
Copy right:	    Coffee Tech.
Author:         jiaoyue
Date:           2021-11-23
Description:    提供共享内存组件
***********************************************************************************/

#include "shmem.h"

#define MAGIC_ID 'j'
/**
 * @brief 初始化共享内存
 * @param para 参数结构体
 * @param name 共享内存标识名称
 * @param size 共享内存大小
 * @param write_mode 是否以写模式打开(1-写，0-读)
 * @return 0成功，-1失败
 */
int shm_init(struct shm_param *para, const char *name, size_t size, int write_mode)
{
    assert(NULL != para);
    assert(NULL != name && strlen(name) > 0);

    key_t key;
    int id;
    char sys_cmd[256];
    char path[256];
    int flags = 0;
    int retsys = 0;
    // 创建目录（如果不存在）
    sprintf(sys_cmd, "mkdir -p /tmp/ipc/shmem");
    retsys = system(sys_cmd);

    sprintf(path, "/tmp/ipc/shmem/%s", name);
    sprintf(sys_cmd, "%s %s", "touch", path);
    retsys = system(sys_cmd); // 创建路径下的文件，确保ftok可用

    if (retsys < 0)
    {
        perror("fail to system");
        return -1;
    }
    // 生成key（确保读写双方路径和MAGIC_ID一致）
    key = ftok(path, MAGIC_ID); // MAGIC_ID需为相同常量（如'A'）
    if (key < 0)
    {
        perror("fail to ftok");
        printf("error :path = %s, MAGIC_ID = %d\n", path, MAGIC_ID);
        return -1;
    }
    printf("shm_init: key = %#x\n", key); // 调试输出key

    // 设置标志位
    if (write_mode)
    {
        // 写模式：创建新的共享内存（若不存在）
        flags = IPC_CREAT | 0666; // 允许创建并设置权限
    }
    else
    {
        // 读模式：仅打开已存在的共享内存
        flags = 0; // 不指定IPC_CREAT，仅通过key查找
    }

    // 获取或创建共享内存
    id = shmget(key, size, flags);
    if (id < 0)
    {
        perror("fail to shmget");
        // 补充：若读模式失败，尝试以写模式重新打开（可选，根据业务需求）
        if (!write_mode)
        {
            printf("尝试以写模式重新打开共享内存...\n");
            id = shmget(key, size, IPC_CREAT | 0666); // 强制打开（可能覆盖，谨慎使用）
            if (id < 0)
            {
                return -1;
            }
        }
        return -1;
    }

    para->id = id;
    para->size = size;
    strncpy(para->name, name, SHM_NAME_SZ);
    para->name[SHM_NAME_SZ] = '\0';

    return 0;
}
/**
 * @brief 获取共享内存地址
 * @param para
 * @return 失败返回NULL
 */
void *shm_getaddr(struct shm_param *para)
{
    void *addr;
    addr = shmat(para->id, NULL, 0);
    if (addr == (void *)-1)
    {
        para->addr = NULL;
    }
    else
    {
        para->addr = addr;
    }

    return para->addr;
}

/**
 * @brief 写共享内存
 * @param para
 * @param data
 * @param size
 */
int shm_write(struct shm_param *shm, const void *data, size_t size)
{
    if (!shm || !shm->addr || !data || size > shm->size)
    {
        return -1; // 参数错误或数据太大
    }

    memcpy(shm->addr, data, size);
    return 0;
}
/**
 * @brief 解除共享内存
 * @param para
 * @return
 */
int shm_del(const struct shm_param *para)
{
    assert(NULL != para);
    int ret = shmdt(para->addr);
    if (ret < 0)
    {
        perror("fail to shmdt");
        return -1;
    }

    ret = shmctl(para->id, IPC_RMID, NULL);
    if (ret < 0)
    {
        perror("fail to shmctl");
        return -1;
    }

    return 0;
}

int shm_detach(struct shm_param *shm)
{
    if (shm->addr != NULL)
    {
        if (shmdt(shm->addr) == -1)
        { // 更正为 shmdt
            perror("shmdt failed");
            return -1;
        }
        shm->addr = NULL;
    }
    return 0;
}